//! ## Code Signature for CS2 System
//!
//! The code signature is used as part of the cache key to identify the code block.
//!
//! For short code, the code signature is the code itself. While for long code, the hash
//! of it is used as the code signature.

use std::{fmt::Display, mem};

use sha1::{Digest, Sha1 as CodeSignHasher};

/// Signature of the code. Used as the key of the cache block.
///
/// Use sha1 to hash the code if the code is longer than 20 bytes.
#[repr(C)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct CodeSign {
    kind: CodeSignKind,
    body: CodeSignBody,
}

#[repr(C)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
enum CodeSignKind {
    Raw(usize),
    Hash,
}

impl CodeSignKind {
    /// Get the length of the code sign.
    #[inline]
    fn len(&self) -> usize {
        if let Self::Raw(len) = self {
            *len
        } else {
            CodeSignBody::LEN
        }
    }
}

#[repr(C)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct CodeSignBody([u8; 20]);

impl CodeSignBody {
    /// The length of the code sign body.
    ///
    /// cbindgen seems to have a bug that it cannot generate macro for constants like:
    ///
    /// ```ignore
    /// struct CodeSignBody([u8; Self::LEN]);
    ///
    /// impl CodeSignBody {
    ///     const LEN: usize = 20;
    /// }
    /// ```
    const LEN: usize = mem::size_of::<Self>();
}

impl<T> From<T> for CodeSign
where
    T: AsRef<[u8]>,
{
    fn from(value: T) -> Self {
        let len = value.as_ref().len();
        if len <= CodeSignBody::LEN {
            let mut body = [0; CodeSignBody::LEN];
            body[..value.as_ref().len()].copy_from_slice(value.as_ref());
            Self {
                kind: CodeSignKind::Raw(len),
                body: CodeSignBody(body),
            }
        } else {
            let body = CodeSignHasher::digest(value.as_ref());
            Self {
                kind: CodeSignKind::Hash,
                body: CodeSignBody(body.into()),
            }
        }
    }
}

impl Display for CodeSign {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut output = String::new();

        output.push_str(match self.kind {
            CodeSignKind::Raw(_) => "(raw) ",
            CodeSignKind::Hash => "(sha) ",
        });

        for byte in &self.body.0[..self.kind.len()] {
            output.push_str(&format!("{:02x}", byte));
        }

        write!(f, "{}", output)
    }
}

#[cfg(test)]
mod test {
    use coverage_helper::test;

    use super::*;

    #[test]
    fn long_code_sign() {
        let code = vec![0; 64];
        let sign = CodeSign::from(&code);
        assert_eq!(sign.kind, CodeSignKind::Hash);
        assert_eq!(sign.body.0.len(), CodeSignBody::LEN);

        let hash = CodeSignHasher::digest(&code);
        let mut hash_str = String::new();
        for byte in hash.as_slice() {
            hash_str.push_str(&format!("{:02x}", byte));
        }

        assert_eq!(format!("{}", sign), format!("(sha) {}", hash_str));
    }
}
